﻿using System;
using System.Collections.Generic;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Color = Microsoft.Xna.Framework.Color;
using Rectangle = Microsoft.Xna.Framework.Rectangle;

namespace Tetris
{
    public class Board
    {
        #region Variables

        private const int HEIGHT = 20;
        private const int WIDTH = 10;
        private const int BLOCKS_COUNT_IN_FIGURE = 4;

        private Texture2D textures;
        private Rectangle[] rectangles;
        private Vector2[,,] figures;
        private readonly Vector2 startingPositionForNewFigure = new Vector2(3, 0);
        private Vector2 positionForDynamicFigure;
        private Vector2[] dynamicFigure = new Vector2[BLOCKS_COUNT_IN_FIGURE];
        private Random randoim = new Random();
        private int[,] boardColor;

        private int dynamicFigureNumber;
        private int dynamicFigureModificationNumber;
        private int dynamicFigureColor;

        private bool blockLine;
        private bool showNewBlock;
        private float movement;
        private float speed;

        private Queue<int> nextFigures = new Queue<int>();
        private Queue<int> nextFiguresModification = new Queue<int>();

        private enum FieldState
        {
            Free,
            Static,
            Dynamic
        };

        private FieldState[,] boardFields;

        #endregion Variables

        #region Properties

        public float Movement
        {
            get { return movement; }
            set { movement = value; }
        }

        public float Speed
        {
            get { return speed; }
            set { speed = value; }
        }

        #endregion Properties

        #region Constructor

        public Board(ref Texture2D textures, Rectangle[] rectangles)
        {
            // Load Textures for blocks
            this.textures = textures;
            // Rectangle to draw figures
            this.rectangles = rectangles;

            // Create tetris board
            boardFields = new FieldState[WIDTH,HEIGHT];
            boardColor = new int[WIDTH,HEIGHT];

            createFigures();
            Initialize();
        }

        #endregion Constructor

        #region Initialization of Figures

        public void Initialize()
        {
            showNewBlock = true;
            movement = 0;
            speed = 0.1f;

            for (int x = 0; x < WIDTH; x++)
            {
                for (int y = 0; y < HEIGHT; y++)
                {
                    ClearBoardField(x, y);
                }
            }
        }

        private void createFigures()
        {
            // Figures[figure's number, figure's modification, figure's block number] = Vector2
            // At all figures is 7, every has 4 modifications (for cube all modifications the same)
            // and every figure consists from 4 blocks
            figures = new Vector2[7, 4, 4];
            // o-figure
            for (int i = 0; i < 4; i++)
            {
                figures[0, i, 0] = new Vector2(1, 0);
                figures[0, i, 1] = new Vector2(2, 0);
                figures[0, i, 2] = new Vector2(1, 1);
                figures[0, i, 3] = new Vector2(2, 1);
            }

            // I-figures
            for (int i = 0; i < 4; i += 2)
            {
                figures[1, i, 0] = new Vector2(0, 0);
                figures[1, i, 1] = new Vector2(1, 0);
                figures[1, i, 2] = new Vector2(2, 0);
                figures[1, i, 3] = new Vector2(3, 0);
                figures[1, i + 1, 0] = new Vector2(1, 0);
                figures[1, i + 1, 1] = new Vector2(1, 1);
                figures[1, i + 1, 2] = new Vector2(1, 2);
                figures[1, i + 1, 3] = new Vector2(1, 3);
            }

            // J-figures
            figures[2, 0, 0] = new Vector2(0, 0);
            figures[2, 0, 1] = new Vector2(1, 0);
            figures[2, 0, 2] = new Vector2(2, 0);
            figures[2, 0, 3] = new Vector2(2, 1);
            figures[2, 1, 0] = new Vector2(2, 0);
            figures[2, 1, 1] = new Vector2(2, 1);
            figures[2, 1, 2] = new Vector2(1, 2);
            figures[2, 1, 3] = new Vector2(2, 2);
            figures[2, 2, 0] = new Vector2(0, 0);
            figures[2, 2, 1] = new Vector2(0, 1);
            figures[2, 2, 2] = new Vector2(1, 1);
            figures[2, 2, 3] = new Vector2(2, 1);
            figures[2, 3, 0] = new Vector2(1, 0);
            figures[2, 3, 1] = new Vector2(2, 0);
            figures[2, 3, 2] = new Vector2(1, 1);
            figures[2, 3, 3] = new Vector2(1, 2);

            // L-figures
            figures[3, 0, 0] = new Vector2(0, 0);
            figures[3, 0, 1] = new Vector2(1, 0);
            figures[3, 0, 2] = new Vector2(2, 0);
            figures[3, 0, 3] = new Vector2(0, 1);
            figures[3, 1, 0] = new Vector2(2, 0);
            figures[3, 1, 1] = new Vector2(2, 1);
            figures[3, 1, 2] = new Vector2(1, 0);
            figures[3, 1, 3] = new Vector2(2, 2);
            figures[3, 2, 0] = new Vector2(0, 1);
            figures[3, 2, 1] = new Vector2(1, 1);
            figures[3, 2, 2] = new Vector2(2, 1);
            figures[3, 2, 3] = new Vector2(2, 0);
            figures[3, 3, 0] = new Vector2(1, 0);
            figures[3, 3, 1] = new Vector2(2, 2);
            figures[3, 3, 2] = new Vector2(1, 1);
            figures[3, 3, 3] = new Vector2(1, 2);

            // S-figures
            for (int i = 0; i < 4; i += 2)
            {
                figures[4, i, 0] = new Vector2(0, 1);
                figures[4, i, 1] = new Vector2(1, 1);
                figures[4, i, 2] = new Vector2(1, 0);
                figures[4, i, 3] = new Vector2(2, 0);
                figures[4, i + 1, 0] = new Vector2(1, 0);
                figures[4, i + 1, 1] = new Vector2(1, 1);
                figures[4, i + 1, 2] = new Vector2(2, 1);
                figures[4, i + 1, 3] = new Vector2(2, 2);
            }

            // Z-figures
            for (int i = 0; i < 4; i += 2)
            {
                figures[5, i, 0] = new Vector2(0, 0);
                figures[5, i, 1] = new Vector2(1, 0);
                figures[5, i, 2] = new Vector2(1, 1);
                figures[5, i, 3] = new Vector2(2, 1);
                figures[5, i + 1, 0] = new Vector2(2, 0);
                figures[5, i + 1, 1] = new Vector2(1, 1);
                figures[5, i + 1, 2] = new Vector2(2, 1);
                figures[5, i + 1, 3] = new Vector2(1, 2);
            }

            // T-figures
            figures[6, 0, 0] = new Vector2(0, 1);
            figures[6, 0, 1] = new Vector2(1, 1);
            figures[6, 0, 2] = new Vector2(2, 1);
            figures[6, 0, 3] = new Vector2(1, 0);
            figures[6, 1, 0] = new Vector2(1, 0);
            figures[6, 1, 1] = new Vector2(1, 1);
            figures[6, 1, 2] = new Vector2(1, 2);
            figures[6, 1, 3] = new Vector2(2, 1);
            figures[6, 2, 0] = new Vector2(0, 0);
            figures[6, 2, 1] = new Vector2(1, 0);
            figures[6, 2, 2] = new Vector2(2, 0);
            figures[6, 2, 3] = new Vector2(1, 1);
            figures[6, 3, 0] = new Vector2(2, 0);
            figures[6, 3, 1] = new Vector2(2, 1);
            figures[6, 3, 2] = new Vector2(2, 2);
            figures[6, 3, 3] = new Vector2(1, 1);

            nextFigures.Enqueue(randoim.Next(7));
            nextFigures.Enqueue(randoim.Next(7));
            nextFigures.Enqueue(randoim.Next(7));
            nextFigures.Enqueue(randoim.Next(7));

            nextFiguresModification.Enqueue(randoim.Next(4));
            nextFiguresModification.Enqueue(randoim.Next(4));
            nextFiguresModification.Enqueue(randoim.Next(4));
            nextFiguresModification.Enqueue(randoim.Next(4));
        }

        #endregion Initialization of Figures
        
        #region Public Methods

        public void FindDynamicFigure()
        {
            int blockNumberInDynamicFigure = 0;
            for (int x = 0; x < WIDTH; x++)
            {
                for (int y = 0; y < HEIGHT; y++)
                {
                    if (boardFields[x,y] == FieldState.Dynamic)
                    {
                        dynamicFigure[blockNumberInDynamicFigure++] = new Vector2(x, y);
                    }
                }
            }
        }
        /// <summary>
        /// Find, destroy and save lines's count
        /// </summary>
        /// <returns>Number of destoyed lines</returns>
        public int DestroyLines()
        {
            // TODO: MOZE BYC PROBLEM Z PENTLA
            // Find total lines
            int blockLineCount = 0;
            for (int y = 0; y < HEIGHT; y++)
            {
                for (int x = 0; x < WIDTH; x++)
                {
                    if (boardFields[x,y] == FieldState.Static)
                    {
                        blockLine = false;
                    }
                    else
                    {
                        blockLine = false;
                        break;
                    }
                }
                // Destroy total lines
                if (blockLine)
                {
                    // Save number of total lines
                    blockLineCount++;
                    for (int l = y - 1; l >= 0; l--)
                    {
                        for (int k = 0; k < WIDTH; k++)
                        {
                            boardFields[k, l] = boardFields[k, l - 1];
                            boardColor[k, l] = boardColor[k, l - 1];
                        }
                    }
                    for (int l = 0; l < WIDTH; l++)
                    {
                        boardFields[l, 0] = FieldState.Free;
                        boardColor[l, 0] = -1;
                    }
                }
            }

            return blockLineCount;
        }

        /// <summary>
        /// Create new shape in the game, if need it
        /// </summary>
        public bool CreateNewFigure()
        {
            if (showNewBlock)
            {
                // Generate new figures shape
                dynamicFigureNumber = nextFigures.Dequeue();
                nextFigures.Enqueue(randoim.Next(7));

                dynamicFigureModificationNumber = nextFiguresModification.Dequeue();
                nextFiguresModification.Enqueue(randoim.Next(4));

                dynamicFigureColor = dynamicFigureNumber;

                // Position and coordinates for new dynamic figure
                positionForDynamicFigure = startingPositionForNewFigure;
                for (int i = 0; i < BLOCKS_COUNT_IN_FIGURE; i++)
                {
                    dynamicFigure[i] = figures[dynamicFigureNumber, dynamicFigureModificationNumber, i] + positionForDynamicFigure;
                }

                if (!drawFigureOnBoard(dynamicFigure, dynamicFigureColor))
                {
                    return false;
                }

                showNewBlock = false;
            }

            return true;
        }

        public void SortingVector2(ref Vector2[] vector, bool sortByX, int a, int b)
        {
            if (a >= b)
                return;

            int i = a;
            for (int j = a; j <= b; j++)
            {
                if (sortByX)
                {
                    if (vector[j].X <= vector[b].X)
                    {
                        Vector2 tempVector = vector[i];
                        vector[i] = vector[j];
                        vector[j] = tempVector;
                        i++;
                    }
                }
                else
                {
                    if (vector[j].Y <= vector[b].Y)
                    {
                        Vector2 tempVector = vector[i];
                        vector[i] = vector[j];
                        vector[j] = tempVector;
                        i++;
                    }
                }
            }

            int c = i - 1;
            SortingVector2(ref vector, sortByX, a, c - 1);
            SortingVector2(ref vector, sortByX, c + 1, b);
        }

        public void MoveFigureLeft()
        {
            // Sorting blocks for dynamic figure to correct moving
            SortingVector2(ref dynamicFigure, true, dynamicFigure.GetLowerBound(0), dynamicFigure.GetUpperBound(0));
            
            // Check collisions
            for (int i = 0; i < BLOCKS_COUNT_IN_FIGURE; i++)
            {
                if ((dynamicFigure[i].X == 0))
                    return;

                if (boardFields[(int)dynamicFigure[i].X - 1, (int)dynamicFigure[i].Y] == FieldState.Static)
                    return;
            }

            // Move figure on board
            for (int i = 0; i < BLOCKS_COUNT_IN_FIGURE; i++)
            {
                boardFields[(int)dynamicFigure[i].X - 1, (int)dynamicFigure[i].Y] = boardFields[(int)dynamicFigure[i].X, (int)dynamicFigure[i].Y];
                boardColor[(int)dynamicFigure[i].X - 1, (int)dynamicFigure[i].Y] = boardColor[(int)dynamicFigure[i].X, (int)dynamicFigure[i].Y];
                ClearBoardField((int) dynamicFigure[i].X, (int) dynamicFigure[i].Y);
                // Change position for blocks in DynamicFigure
                dynamicFigure[i].X = dynamicFigure[i].X - 1;
            }

            // Change position vector
            //if (PositionForDynamicFigure.X > 0)
            positionForDynamicFigure.X--;
        }

        public void MoveFigureRight()
        {
            // Sorting blocks for dynamic figure to correct moving
            SortingVector2(ref dynamicFigure, true, dynamicFigure.GetLowerBound(0), dynamicFigure.GetUpperBound(0));
            // Check collisions
            for (int i = 0; i < BLOCKS_COUNT_IN_FIGURE; i++)
            {
                if ((dynamicFigure[i].X == WIDTH - 1))
                    return;
                if (boardFields[(int)dynamicFigure[i].X + 1, (int)dynamicFigure[i].Y] == FieldState.Static)
                    return;
            }
            // Move figure on board
            for (int i = BLOCKS_COUNT_IN_FIGURE - 1; i >= 0; i--)
            {
                boardFields[(int)dynamicFigure[i].X + 1, (int)dynamicFigure[i].Y] =
                    boardFields[(int)dynamicFigure[i].X, (int)dynamicFigure[i].Y];
                boardColor[(int)dynamicFigure[i].X + 1, (int)dynamicFigure[i].Y] =
                    boardColor[(int)dynamicFigure[i].X, (int)dynamicFigure[i].Y];
                ClearBoardField((int)dynamicFigure[i].X, (int)dynamicFigure[i].Y);
                // Change position for blocks in DynamicFigure
                dynamicFigure[i].X = dynamicFigure[i].X + 1;
            }
            // Change position vector
            //if (PositionForDynamicFigure.X < width - 1)
            positionForDynamicFigure.X++;
        }

        public void MoveFigureDown()
        {
            // Sorting blocks for dynamic figure to correct moving
            SortingVector2(ref dynamicFigure, false, dynamicFigure.GetLowerBound(0), dynamicFigure.GetUpperBound(0));
            // Check colisions
            for (int i = 0; i < BLOCKS_COUNT_IN_FIGURE; i++)
            {
                if ((dynamicFigure[i].Y == HEIGHT - 1))
                {
                    for (int k = 0; k < BLOCKS_COUNT_IN_FIGURE; k++)
                        boardFields[(int)dynamicFigure[k].X, (int)dynamicFigure[k].Y] = FieldState.Static;
                    showNewBlock = true;
                    return;
                }
                if (boardFields[(int)dynamicFigure[i].X, (int)dynamicFigure[i].Y + 1] == FieldState.Static)
                {
                    for (int k = 0; k < BLOCKS_COUNT_IN_FIGURE; k++)
                        boardFields[(int)dynamicFigure[k].X, (int)dynamicFigure[k].Y] = FieldState.Static;
                    showNewBlock = true;
                    return;
                }
            }
            // Move figure on board
            for (int i = BLOCKS_COUNT_IN_FIGURE - 1; i >= 0; i--)
            {
                boardFields[(int)dynamicFigure[i].X, (int)dynamicFigure[i].Y + 1] = boardFields[(int)dynamicFigure[i].X, (int)dynamicFigure[i].Y];
                boardColor[(int)dynamicFigure[i].X, (int)dynamicFigure[i].Y + 1] = boardColor[(int)dynamicFigure[i].X, (int)dynamicFigure[i].Y];
                ClearBoardField((int)dynamicFigure[i].X, (int)dynamicFigure[i].Y);
                // Change position for blocks in DynamicFigure
                dynamicFigure[i].Y = dynamicFigure[i].Y + 1;
            }
            // Change position vector
            //if (PositionForDynamicFigure.Y < height - 1)
            positionForDynamicFigure.Y++;
        }

        public void RotateFigure()
        {
            // Check colisions for next modification
            Vector2[] testDynamicFigure = new Vector2[dynamicFigure.GetUpperBound(0) + 1];
            for (int i = 0; i < BLOCKS_COUNT_IN_FIGURE; i++)
                testDynamicFigure[i] = figures[dynamicFigureNumber, (dynamicFigureModificationNumber + 1) % 4, i] +
                                       positionForDynamicFigure;

            // Make sure that figure can rotate if she stand near left and right borders
            SortingVector2(ref testDynamicFigure, true, testDynamicFigure.GetLowerBound(0),
                           testDynamicFigure.GetUpperBound(0));
            int leftFigureBound;
            int rightFigureBound;

            if ((leftFigureBound = (int)testDynamicFigure[0].X) < 0)
            {
                //int leftFigureBound = (int)TestDynamicFigure[0].X;
                for (int i = 0; i < BLOCKS_COUNT_IN_FIGURE; i++)
                {
                    testDynamicFigure[i] += new Vector2(0 - leftFigureBound, 0);
                }

                if (tryPlaceFigureOnBoard(testDynamicFigure))
                    positionForDynamicFigure += new Vector2(0 - leftFigureBound, 0);
            }
            if ((rightFigureBound = (int)testDynamicFigure[BLOCKS_COUNT_IN_FIGURE - 1].X) >= WIDTH)
            {
                //int rightFigureBound = (int)TestDynamicFigure[BlocksCountInFigure - 1].X;
                for (int i = 0; i < BLOCKS_COUNT_IN_FIGURE; i++)
                {
                    testDynamicFigure[i] -= new Vector2(rightFigureBound - WIDTH + 1, 0);
                }

                if (tryPlaceFigureOnBoard(testDynamicFigure))
                    positionForDynamicFigure -= new Vector2(rightFigureBound - WIDTH + 1, 0);
            }

            if (tryPlaceFigureOnBoard(testDynamicFigure))
            {
                dynamicFigureModificationNumber = (dynamicFigureModificationNumber + 1) % 4;
                // Clear dynamic fields
                for (int i = 0; i <= dynamicFigure.GetUpperBound(0); i++)
                    ClearBoardField((int)dynamicFigure[i].X, (int)dynamicFigure[i].Y);
                dynamicFigure = testDynamicFigure;
                for (int i = 0; i <= dynamicFigure.GetUpperBound(0); i++)
                {
                    boardFields[(int) dynamicFigure[i].X, (int) dynamicFigure[i].Y] = FieldState.Dynamic;
                    boardColor[(int) dynamicFigure[i].X, (int) dynamicFigure[i].Y] = dynamicFigureColor;
                }
            }
        }

        public void Draw(SpriteBatch spriteBatch)
        {
            Vector2 startPosition;
            // Draw the blocks
            for (int x = 0; x < WIDTH; x++)
                for (int y = 0; y < HEIGHT; y++)
                    if (boardFields[x, y] != FieldState.Free)
                    {
                        startPosition = new Vector2((10 + x) * rectangles[0].Width, (2 + y) * rectangles[0].Height);
                        spriteBatch.Draw(textures, startPosition - new Vector2(24*8, 0), rectangles[boardColor[x, y]], Color.White);
                    }

            // Draw next figures
            Queue<int>.Enumerator figure = nextFigures.GetEnumerator();
            Queue<int>.Enumerator modification = nextFiguresModification.GetEnumerator();
            for (int i = 0; i < nextFigures.Count; i++)
            {
                figure.MoveNext();
                modification.MoveNext();
                for (int j = 0; j < BLOCKS_COUNT_IN_FIGURE; j++)
                {
                    startPosition = rectangles[0].Height * (new Vector2(24, 3 + 5 * i) + figures[figure.Current, modification.Current, j]);
                    spriteBatch.Draw(textures, startPosition - new Vector2(24 * 8, 0), rectangles[figure.Current], Color.White);
                }
            }
        }

        #endregion Public Methods

        #region Private Methods
        
        private void ClearBoardField(int x, int y)
        {
            boardFields[x, y] = FieldState.Free;
            boardColor[x, y] = -1;
        }

        bool drawFigureOnBoard(Vector2[] vector, int color)
        {
            if (!tryPlaceFigureOnBoard(vector))
                return false;
            for (int i = 0; i <= vector.GetUpperBound(0); i++)
            {
                boardFields[(int)vector[i].X, (int)vector[i].Y] = FieldState.Dynamic;
                boardColor[(int)vector[i].X, (int)vector[i].Y] = color;
            }
            return true;
        }

        bool tryPlaceFigureOnBoard(Vector2[] vector)
        {
            for (int i = 0; i <= vector.GetUpperBound(0); i++)
            {
                if ((vector[i].X < 0) || (vector[i].X >= WIDTH) || (vector[i].Y >= HEIGHT))
                    return false;
            }
            for (int i = 0; i <= vector.GetUpperBound(0); i++)
            {
                if (boardFields[(int)vector[i].X, (int)vector[i].Y] == FieldState.Static)
                    return false;
            }

            return true;
        }
        #endregion Private Methods
    }
}
